#pragma once

#include <stx/core/libx.hpp>

inline namespace stx
{
DEFINE_SAFE_BOOL(SbLowercase);

// Make std::optional monadic.
// e.g. auto r = opt | f1 | f2 | f3;
constexpr auto operator|(IsStdOptional auto&&                       opt,
                         IsCallable<decltype(FORWARD(*opt))> auto&& fn)
    -> std::optional<NoCvr<decltype(std::invoke(fn, FORWARD(*opt)))>>
{
    if (!opt)
    {
        return std::nullopt;
    }

    try
    {
        return std::invoke(fn, FORWARD(*opt));
    }
    catch (std::exception const& e)
    {
        gLastError().set(e.what());
    }
    catch (...)
    {
        gLastError().set(-1);
    }

    return std::nullopt;
}

[[nodiscard]] constexpr auto adjust(std::input_iterator auto pos,
                                    IsIntegral auto const    n)
{
    using T =
        typename std::iterator_traits<NoCvr<decltype(pos)>>::difference_type;
    std::advance(pos, static_cast<T>(n));
    return pos;
}

template<IsRawPod T>
struct DefaultInitAllocator__
{
    using value_type = T;

    [[nodiscard]] constexpr value_type* allocate(std::size_t const n)
    {
        return new value_type[n];
    }

    constexpr void deallocate(value_type* const p, std::size_t)
    {
        delete[] p;
    };

    constexpr void construct(value_type*, auto&&...)
    {}

    void destroy(auto*)
    {}
};

template<IsRawPod T, IsRawPod U>
constexpr bool operator==(DefaultInitAllocator__<T> const,
                          DefaultInitAllocator__<U> const)
{
    return true;
}

template<IsRawPod                               T,
         IsIdentical<DefaultInitAllocator__<T>> A = DefaultInitAllocator__<T>>
class PodBuffer final : private std::vector<T, A>
{
    using Base_ = std::vector<T, A>;

public:
    using typename Base_::value_type;
    using typename Base_::allocator_type;
    using typename Base_::size_type;
    using typename Base_::difference_type;
    using typename Base_::reference;
    using typename Base_::const_reference;
    using typename Base_::pointer;
    using typename Base_::const_pointer;
    using typename Base_::iterator;
    using typename Base_::const_iterator;
    using typename Base_::reverse_iterator;
    using typename Base_::const_reverse_iterator;

    using Base_::Base_;
    using Base_::get_allocator;
    using Base_::begin;
    using Base_::end;
    using Base_::cbegin;
    using Base_::cend;
    using Base_::rbegin;
    using Base_::rend;
    using Base_::crbegin;
    using Base_::crend;
    using Base_::operator[];
    using Base_::front;
    using Base_::back;
    using Base_::data;
    using Base_::empty;
    using Base_::size;
    using Base_::max_size;
    using Base_::capacity;
    using Base_::clear;
    using Base_::swap;
    using Base_::pop_back;

    [[nodiscard]] constexpr auto
    operator<=>(PodBuffer const& other) const noexcept
    {
        if (this == &other)
        {
            return std::strong_ordering::equal;
        }

        return threeWayCompare__(*this, other);
    }

    [[nodiscard]] constexpr bool
    operator==(PodBuffer const& other) const noexcept
    {
        if (this == &other)
        {
            return true;
        }

        return 0 == threeWayCompare__(*this, other);
    }

    PodBuffer(PodBuffer const& other) : PodBuffer(other, allocator_type{})
    {}

    DEFINE_COPY_SEMANTICS(PodBuffer, PodBuffer const&);

    DEFINE_MOVE_SEMANTICS(PodBuffer, {
        static_cast<Base_&>(*this) =
            std::exchange(static_cast<Base_&>(other), {});
    });

    constexpr PodBuffer(size_type             count,
                        value_type const&     value,
                        allocator_type const& alloc = allocator_type{})
        : Base_(alloc)
    {
        this->assign(count, value);
    }

    template<std::input_iterator InputIter_>
    constexpr PodBuffer(InputIter_            first,
                        InputIter_            last,
                        allocator_type const& alloc = allocator_type{})
        : Base_(alloc)
    {
        this->assign(first, last);
    }

    constexpr PodBuffer(PodBuffer const& other, allocator_type const& alloc)
        : Base_(other.size(), alloc)
    {
        if (!this->empty())
        {
            std::memcpy(
                this->data(), other.data(), this->size() * sizeof(value_type));
        }
    }

    constexpr PodBuffer(PodBuffer&& other, allocator_type const& alloc)
        : Base_(std::move(other), alloc)
    {}

    constexpr PodBuffer(std::initializer_list<value_type> il,
                        allocator_type const& alloc = allocator_type{})
        : Base_(alloc)
    {
        this->assign(il.begin(), il.end());
    }

    constexpr void assign(size_type count, value_type const& value)
    {
        this->clear();
        if (count > this->capacity())
        {
            this->reserve(count);
        }

        this->resize(count);
        LOOP (i, count)
        {
            (*this)[i] = value;
        }
    }

    template<std::input_iterator InputIter_>
    constexpr void assign(InputIter_ first, InputIter_ last)
    {
        auto const count = static_cast<size_type>(std::distance(first, last));
        this->clear();

        if (count > this->capacity())
        {
            this->reserve(count);
        }

        this->resize(count);
        std::copy(first, last, this->begin());
    }

    constexpr void assign(std::initializer_list<value_type> il)
    {
        this->assign(il.begin(), il.end());
    }

    constexpr void reserve(size_type new_cap)
    {
        if (new_cap > this->capacity())
        {
            auto tmp = PodBuffer(new_cap);
            tmp.resize(this->size());
            std::memcpy(
                tmp.data(), this->data(), this->size() * sizeof(value_type));
            this->swap(tmp);
        }
    }

    constexpr void shrink_to_fit()
    {
        if (this->size() < this->capacity())
        {
            auto tmp = PodBuffer(this->size());
            std::memcpy(
                tmp.data(), this->data(), this->size() * sizeof(value_type));
            this->swap(tmp);
        }
    }

    constexpr void resize(size_type count)
    {
        if (count > this->size())
        {
            this->reserve(count);
        }

        Base_::resize(count);
    }

    constexpr void resize(size_type count, const value_type& value)
    {
        auto const old_size = this->size();
        this->resize(count);
        for (auto idx = old_size; idx < count; ++idx)
        {
            (*this)[idx] = value;
        }
    }

    constexpr iterator erase(const_iterator pos)
    {
        if (pos == this->cend())
        {
            return this->end();
        }

        auto const distance = std::distance(this->cbegin(), pos);
        auto       new_pos  = adjust(this->begin(), distance);
        std::move(adjust(new_pos, 1), this->end(), new_pos);
        this->resize(this->size() - 1);

        return new_pos;
    }

    constexpr iterator erase(const_iterator first, const_iterator last)
    {
        auto new_pos = this->begin() + std::distance(this->cbegin(), first);
        if (first == last)
        {
            return new_pos;
        }

        if (last < this->cend())
        {
            std::move(
                adjust(this->begin(), std::distance(this->cbegin(), last)),
                this->end(),
                new_pos);
        }

        this->resize(this->size() -
                     static_cast<size_type>(std::distance(first, last)));
        return new_pos;
    }

    template<IsNonVoid... Args_>
    constexpr reference emplace_back(Args_&&... args)
    {
        if (this->size() >= this->capacity())
        {
            auto tmp = PodBuffer(this->calcLargerCapacity_());
            tmp.resize(this->size());
            std::memcpy(
                tmp.data(), this->data(), this->size() * sizeof(value_type));
            this->swap(tmp);
        }

        this->resize(this->size() + 1);
        new (&this->back()) value_type(FORWARD(args)...);
        return this->back();
    }

    constexpr void push_back(value_type const& value)
    {
        this->emplace_back(value_type(value));
    }

    constexpr void push_back(value_type&& value)
    {
        this->emplace_back(FORWARD(value));
    }

    template<IsNonVoid... Args_>
    constexpr iterator emplace(const_iterator pos, Args_&&... args)
    {
        auto const distance = std::distance(this->cbegin(), pos);

        if (this->size() >= this->capacity())
        {
            auto tmp = PodBuffer(this->calcLargerCapacity_());
            tmp.resize(this->size());

            auto const left_size  = static_cast<size_type>(distance);
            auto const right_size = this->size() - left_size;

            std::memcpy(
                tmp.data(), this->data(), left_size * sizeof(value_type));
            std::memcpy(tmp.data() + left_size + 1uz,
                        this->data() + left_size,
                        right_size * sizeof(value_type));

            new (&*adjust(tmp.begin(), left_size)) value_type(FORWARD(args)...);
            tmp.resize(this->size() + 1);
            this->swap(tmp);

            return adjust(this->begin(), left_size);
        }

        std::move_backward(adjust(this->begin(), distance),
                           this->end(),
                           adjust(this->end(), 1));
        new (&*adjust(this->begin(), distance)) value_type(FORWARD(args)...);
        this->resize(this->size() + 1);

        return adjust(this->begin(), distance);
    }

    constexpr iterator insert(const_iterator pos, value_type const& value)
    {
        return this->emplace(pos, value);
    }

    constexpr iterator insert(const_iterator pos, value_type&& value)
    {
        return this->emplace(pos, FORWARD(value));
    }

    constexpr iterator
    insert(const_iterator pos, size_type count, value_type const& value)
    {
        auto const v =
            views::repeat_n(value, static_cast<std::ptrdiff_t>(count)) |
            views::common;
        return this->insert(pos, v.begin(), v.end());
    }

    constexpr iterator insert(const_iterator                    pos,
                              std::initializer_list<value_type> il)
    {
        return this->insert(pos, il.begin(), il.end());
    }

    template<std::input_iterator InputIter_>
    constexpr iterator
    insert(const_iterator pos, InputIter_ first, InputIter_ last)
    {
        auto const distance = std::distance(this->cbegin(), pos);
        auto const count = static_cast<size_type>(std::distance(first, last));

        if (0 == count)
        {
            return adjust(this->begin(), distance);
        }

        if (this->size() + count > this->capacity())
        {
            auto tmp = PodBuffer(this->size() + count);
            tmp.resize(this->size() + count);

            auto const left_size  = static_cast<size_type>(distance);
            auto const right_size = this->size() - left_size;

            std::memcpy(
                tmp.data(), this->data(), left_size * sizeof(value_type));
            std::memcpy(tmp.data() + left_size + count,
                        this->data() + left_size,
                        right_size * sizeof(value_type));

            auto i = 0uz;
            for (auto tmp_pos = first; tmp_pos != last; ++tmp_pos, ++i)
            {
                new (tmp.data() + left_size + i) value_type(*tmp_pos);
            }

            this->swap(tmp);
            return adjust(this->begin(), left_size);
        }

        auto new_end = adjust(this->end(), count);
        std::move_backward(
            adjust(this->begin(), distance), this->end(), new_end);

        auto i = 0uz;
        for (auto tmp_pos = first; tmp_pos != last; ++tmp_pos, ++i)
        {
            new (this->data() + distance + i) value_type(*tmp_pos);
        }

        this->resize(this->size() + count);
        return adjust(this->begin(), distance);
    }

    [[nodiscard]] std::string_view tosv() const noexcept
    {
        return std::string_view(std::bit_cast<char const*>(this->data()),
                                this->size());
    }

    [[nodiscard]] std::string tos() const noexcept
    {
        return std::string(std::bit_cast<char const*>(this->data()),
                           this->size());
    }

private:
    [[nodiscard]] constexpr size_type calcLargerCapacity_() const noexcept
    {
        return this->size() * 15 / 10 + 1;
    }
};

using Buffer = PodBuffer<std::byte>;

// Class DiagnoseType__ has no definition, so the compiler will report errors
// that contains the deduced template type name. Use this helper class, we can
// get the exact type name after deduction.
template<IsAnyType>
class DiagnoseType__;

template<IsNonVoid... Args_>
std::scoped_lock<Args_...> getLockGuardType__(Args_&&...) noexcept;

template<IsArithmetic T>
[[nodiscard]] constexpr auto min() noexcept
{
    return std::numeric_limits<NoCvr<T>>::min();
}

[[nodiscard]] constexpr auto min(IsArithmetic auto const arg) noexcept
{
    return min<decltype(arg)>();
}

template<IsArithmetic T>
[[nodiscard]] constexpr auto max() noexcept
{
    return std::numeric_limits<NoCvr<T>>::max();
}

[[nodiscard]] constexpr auto max(IsArithmetic auto const arg) noexcept
{
    return max<decltype(arg)>();
}

[[nodiscard]] constexpr bool isMin(IsArithmetic auto const arg) noexcept
{
    return min<decltype(arg)>() == arg;
}

[[nodiscard]] constexpr bool isMax(IsArithmetic auto const arg) noexcept
{
    return max<decltype(arg)>() == arg;
}

[[nodiscard]] constexpr bool isHexChar(IsOctet auto const octet) noexcept
{
    auto const c = static_cast<char>(octet);
    return EXPR((c >= '0') && (c <= '9')) || EXPR((c >= 'a') && (c <= 'f')) ||
           EXPR((c >= 'A') && (c <= 'F'));
}

[[nodiscard]] constexpr bool isAlphanumeric(IsOctet auto const octet) noexcept
{
    auto const c = static_cast<char>(octet);
    return EXPR((c >= '0') && (c <= '9')) || EXPR((c >= 'a') && (c <= 'z')) ||
           EXPR((c >= 'A') && (c <= 'Z'));
}

class Number;

template<typename T>
concept IsNumberLike =
    IsArithmetic<T> || IsSafeNumber<T> || IsIdentical<T, Number>;

class Number final
{
    std::variant<std::int64_t, std::uint64_t, long double> value_;

private:
    template<IsRawIntegral Integer_>
    [[nodiscard]] constexpr Integer_ convertToInteger_() const noexcept
    {
        if (this->isInt64()) [[likely]]
        {
            auto const n = this->getInt64();
            if (std::cmp_greater_equal(n, max<Integer_>())) [[unlikely]]
            {
                return max<Integer_>();
            }
            else if (std::cmp_less_equal(n, min<Integer_>())) [[unlikely]]
            {
                return min<Integer_>();
            }
            else [[likely]]
            {
                return static_cast<Integer_>(n);
            }
        }
        else if (this->isUint64()) [[likely]]
        {
            auto const n = this->getUint64();
            if (std::cmp_greater_equal(n, max<Integer_>())) [[unlikely]]
            {
                return max<Integer_>();
            }
            else if (std::cmp_less_equal(n, min<Integer_>())) [[unlikely]]
            {
                return min<Integer_>();
            }
            else [[likely]]
            {
                return static_cast<Integer_>(n);
            }
        }
        else [[unlikely]]
        {
            return static_cast<Integer_>(this->getLongDouble());
        }
    }

    template<IsRawFloatingPoint FloatingPoint_>
    [[nodiscard]] constexpr FloatingPoint_
    convertToFloatingPoint_() const noexcept
    {
        auto ld = static_cast<long double>(0.0);

        if (this->isLongDouble()) [[likely]]
        {
            ld = static_cast<long double>(this->getLongDouble());
        }
        else if (this->isInt64()) [[unlikely]]
        {
            ld = static_cast<long double>(this->getInt64());
        }
        else
        {
            ld = static_cast<long double>(this->getUint64());
        }

        if (ld >= static_cast<long double>(max<FloatingPoint_>())) [[unlikely]]
        {
            return max<FloatingPoint_>();
        }
        else if (ld <= static_cast<long double>(min<FloatingPoint_>()))
            [[unlikely]]
        {
            return min<FloatingPoint_>();
        }
        else [[likely]]
        {
            return static_cast<FloatingPoint_>(ld);
        }
    }

public:
    constexpr Number() noexcept : value_(std::int64_t{})
    {}

    // NOLINTNEXTLINE
    constexpr Number(IsNumberLike auto const value) noexcept
    {
        using T_ = NoCvr<decltype(value)>;

        if constexpr (IsFloatingPoint<T_>)
        {
            this->value_ = static_cast<long double>(value);
        }
        else if constexpr (IsIntegral<T_>)
        {
            if constexpr (std::is_signed_v<T_>)
            {
                this->value_ = static_cast<std::int64_t>(value);
            }
            else
            {
                this->value_ = static_cast<std::uint64_t>(value);
            }
        }
        else if constexpr (IsSafeNumber<T_>)
        {
            *this = value.value;
        }
        else
        {
            static_assert(IsIdentical<T_, Number>);
            if (value.isInt64())
            {
                this->value_ = value.getInt64();
            }
            else if (value.isUint64())
            {
                this->value_ = value.getUint64();
            }
            else
            {
                this->value_ = value.getLongDouble();
            }
        }
    }

    DEFINE_COPY_SEMANTICS(Number, IsArithmetic auto const&);

    [[nodiscard]] std::size_t getHashCode() const noexcept
    {
        return static_cast<std::size_t>(*this);
    }

    [[nodiscard]] constexpr bool isInt64() const noexcept
    {
        return std::holds_alternative<std::int64_t>(this->value_);
    }

    [[nodiscard]] constexpr bool isUint64() const noexcept
    {
        return std::holds_alternative<std::uint64_t>(this->value_);
    }

    [[nodiscard]] constexpr bool isLongDouble() const noexcept
    {
        return std::holds_alternative<long double>(this->value_);
    }

#define DEFINE_INTEGRAL_TYPE_CONVERT__(...)                       \
    [[nodiscard]] constexpr operator __VA_ARGS__() const noexcept \
    {                                                             \
        return this->template convertToInteger_<__VA_ARGS__>();   \
    }                                                             \
    static_assert(true)

#define DEFINE_FLOATING_TYPE_CONVERT__(...)                           \
    [[nodiscard]] constexpr operator __VA_ARGS__() const noexcept     \
    {                                                                 \
        return this->template convertToFloatingPoint_<__VA_ARGS__>(); \
    }                                                                 \
    static_assert(true)

    DEFINE_INTEGRAL_TYPE_CONVERT__(std::int8_t);
    DEFINE_INTEGRAL_TYPE_CONVERT__(std::int16_t);
    DEFINE_INTEGRAL_TYPE_CONVERT__(std::int32_t);
    DEFINE_INTEGRAL_TYPE_CONVERT__(std::int64_t);
    DEFINE_INTEGRAL_TYPE_CONVERT__(std::uint8_t);
    DEFINE_INTEGRAL_TYPE_CONVERT__(std::uint16_t);
    DEFINE_INTEGRAL_TYPE_CONVERT__(std::uint32_t);
    DEFINE_INTEGRAL_TYPE_CONVERT__(std::uint64_t);
    DEFINE_FLOATING_TYPE_CONVERT__(float);
    DEFINE_FLOATING_TYPE_CONVERT__(double);
    DEFINE_FLOATING_TYPE_CONVERT__(long double);

    [[nodiscard]] friend constexpr Number
    operator+(IsNumberLike auto const a, IsNumberLike auto const b) noexcept
    {
        auto const n1 = Number(a);
        auto const n2 = Number(b);

        if (n1.isLongDouble() || n2.isLongDouble())
        {
            return Number(static_cast<long double>(a) +
                          static_cast<long double>(b));
        }

        if (n1.isInt64() || n2.isInt64())
        {
            return Number(static_cast<std::int64_t>(a) +
                          static_cast<std::int64_t>(b));
        }

        return Number(static_cast<std::uint64_t>(a) +
                      static_cast<std::uint64_t>(b));
    }

    [[nodiscard]] friend constexpr Number
    operator-(IsNumberLike auto const a, IsNumberLike auto const b) noexcept
    {
        auto const n1 = Number(a);
        auto const n2 = Number(b);

        if (n1.isLongDouble() || n2.isLongDouble())
        {
            return Number(static_cast<long double>(a) -
                          static_cast<long double>(b));
        }

        return Number(static_cast<std::int64_t>(a) -
                      static_cast<std::int64_t>(b));
    }

    [[nodiscard]] friend constexpr Number
    operator*(IsNumberLike auto const a, IsNumberLike auto const b) noexcept
    {
        auto const n1 = Number(a);
        auto const n2 = Number(b);

        if (n1.isLongDouble() || n2.isLongDouble())
        {
            return Number(static_cast<long double>(a) *
                          static_cast<long double>(b));
        }

        if (n1.isInt64() || n2.isInt64())
        {
            return Number(static_cast<std::int64_t>(a) *
                          static_cast<std::int64_t>(b));
        }

        return Number(static_cast<std::uint64_t>(a) *
                      static_cast<std::uint64_t>(b));
    }

    [[nodiscard]] friend constexpr Number
    operator/(IsNumberLike auto const a, IsNumberLike auto const b) noexcept
    {
        auto const n1 = Number(a);
        auto const n2 = Number(b);

        if (n1.isLongDouble() || n2.isLongDouble())
        {
            return Number(static_cast<long double>(a) /
                          static_cast<long double>(b));
        }

        if (n1.isInt64() || n2.isInt64())
        {
            return Number(static_cast<std::int64_t>(a) /
                          static_cast<std::int64_t>(b));
        }

        return Number(static_cast<std::uint64_t>(a) /
                      static_cast<std::uint64_t>(b));
    }

    [[nodiscard]] friend constexpr Number
    operator%(IsNumberLike auto const a, IsNumberLike auto const b) noexcept
    {
        auto const n1 = Number(a);
        auto const n2 = Number(b);

        if (n1.isLongDouble() || n2.isLongDouble())
        {
            return {};
        }

        if (n1.isInt64())
        {
            return Number(static_cast<std::int64_t>(a) %
                          static_cast<std::int64_t>(b));
        }

        return Number(static_cast<std::uint64_t>(a) %
                      static_cast<std::uint64_t>(b));
    }

    [[nodiscard]] constexpr std::int64_t getInt64() const noexcept
    {
        return std::get<std::int64_t>(this->value_);
    }

    [[nodiscard]] constexpr std::uint64_t getUint64() const noexcept
    {
        return std::get<std::uint64_t>(this->value_);
    }

    [[nodiscard]] constexpr long double getLongDouble() const noexcept
    {
        return std::get<long double>(this->value_);
    }

    [[nodiscard]] constexpr operator bool() const noexcept // NOLINT
    {
        if (this->isInt64()) [[likely]]
        {
            return static_cast<bool>(this->getInt64());
        }
        else if (this->isUint64()) [[likely]]
        {
            return static_cast<bool>(this->getUint64());
        }

        return true;
    }

    Number& operator++();
    Number  operator++(int);
    Number& operator--();
    Number  operator--(int);
    Number& operator+=(Number const n);
    Number& operator-=(Number const n);
    Number& operator*=(Number const n);
    Number& operator/=(Number const n);
    Number& operator%=(Number const n);
}; // namespace stx

[[nodiscard]] constexpr auto operator<=>(IsNumberLike auto const a,
                                         IsNumberLike auto const b) noexcept
{
    auto const a_ = Number(a);
    auto const b_ = Number(b);

    if (a_.isLongDouble() || b_.isLongDouble())
    {
        auto const l = a_.getLongDouble();
        auto const r = b_.getLongDouble();

        if (l < r)
        {
            return std::strong_ordering::less;
        }
        else if (l > r)
        {
            return std::strong_ordering::greater;
        }
        else
        {
            return std::strong_ordering::equal;
        }
    }

    if (a_.isInt64() && b_.isInt64())
    {
        return a_.getInt64() <=> b_.getInt64();
    }

    if (a_.isUint64() && b_.isUint64())
    {
        return a_.getUint64() <=> b_.getUint64();
    }

    if (a_.isInt64() && b_.isUint64())
    {
        auto const l = a_.getInt64();
        auto const r = b_.getUint64();

        if (std::cmp_less(l, r))
        {
            return std::strong_ordering::less;
        }
        else if (std::cmp_greater(l, r))
        {
            return std::strong_ordering::greater;
        }
        else
        {
            return std::strong_ordering::equal;
        }
    }

    auto const l = a_.getUint64();
    auto const r = b_.getInt64();

    if (std::cmp_less(l, r))
    {
        return std::strong_ordering::less;
    }
    else if (std::cmp_greater(l, r))
    {
        return std::strong_ordering::greater;
    }
    else
    {
        return std::strong_ordering::equal;
    }
}

[[nodiscard]] constexpr bool operator==(IsNumberLike auto const a,
                                        IsNumberLike auto const b) noexcept
{
    auto const a_ = Number(a);
    auto const b_ = Number(b);

    if (a_.isLongDouble() || b_.isLongDouble())
    {
        return static_cast<long double>(a_) == static_cast<long double>(b_);
    }

    if (a_.isInt64() && b_.isInt64())
    {
        return a_.getInt64() == b_.getInt64();
    }

    if (a_.isUint64() && b_.isUint64())
    {
        return a_.getUint64() == b_.getUint64();
    }

    if (a_.isInt64() && b_.isUint64())
    {
        return std::cmp_equal(a_.getInt64(), b_.getUint64());
    }

    return std::cmp_equal(a_.getUint64(), b_.getInt64());
}

DEFINE_SAFE_NUMBER(UniqueId, std::uint64_t, 0uz);
[[nodiscard]] UniqueId getUniqueId();

// - "0x" or "0X" prefixes are not recognized for base 16.
// - Only the minus sign is recognized (not the plus sign),
//   and only for signed integer types of value.
// - "base" is a value between 2 and 36 (inclusive).

// Examples:
// ---------
// ton<int>("12345"sv);    // ok
// ton<int>("-12345"sv);   // ok
// ton<int>("abcd"sv, 16); // ok

// ton<int>("+12345"sv);     // error
// ton<int>("0xabcd"sv, 16); // error
// ton<int>("0Xabcd"sv, 16); // error
enum class NumberBase
{
    K_2 = 2,
    K_3,
    K_4,
    K_5,
    K_6,
    K_7,
    K_8,
    K_9,
    K_10,
    K_11,
    K_12,
    K_13,
    K_14,
    K_15,
    K_16,
    K_17,
    K_18,
    K_19,
    K_20,
    K_21,
    K_22,
    K_23,
    K_24,
    K_25,
    K_26,
    K_27,
    K_28,
    K_29,
    K_30,
    K_31,
    K_32,
    K_33,
    K_34,
    K_35,
    K_36,
};

[[nodiscard]] constexpr Number ton(IsEnum auto const e) noexcept
{
    return std::to_underlying(e);
}

[[nodiscard]] std::optional<Number> ton(std::string_view const sv,
                                        NumberBase const       base);

[[nodiscard]] std::optional<Number> ton(std::string_view const sv);

[[nodiscard]] constexpr auto getMemAreaInfo(IsMemArea auto&& v) noexcept
{
    if constexpr (IsContiguousPodRange<decltype(v)>)
    {
        if constexpr (IsConst<decltype(*std::data(v))>)
        {
            return std::pair(std::bit_cast<std::byte const*>(std::data(v)),
                             std::size(v) * sizeof(*std::data(v)));
        }
        else
        {
            return std::pair(std::bit_cast<std::byte*>(std::data(v)),
                             std::size(v) * sizeof(*std::data(v)));
        }
    }
    else
    {
        static_assert(IsPod<decltype(v)>);

        if constexpr (IsConst<decltype(v)>)
        {
            return std::pair(std::bit_cast<std::byte const*>(&v), sizeof(v));
        }
        else
        {
            return std::pair(std::bit_cast<std::byte*>(&v), sizeof(v));
        }
    }
}

class MemView final : public std::span<std::byte const>
{
    using Base_ = std::span<std::byte const>;

public:
    constexpr MemView() noexcept = default;

    template<IsMemArea T>
    requires EXPR(!IsConst<T>)
    explicit(
        !(IsIdentical<T, Buffer> || IsIdentical<T, std::string> ||
          IsIdentical<T, std::string_view>)) constexpr MemView(T const&
                                                                   mm) noexcept
        : Base_(getMemAreaInfo(mm).first, getMemAreaInfo(mm).second)
    {}

    constexpr MemView(void const* const       p,
                      IsNumberLike auto const size) noexcept
        : Base_(std::bit_cast<std::byte const*>(p), Number(size))
    {}

    [[nodiscard]] constexpr operator std::string_view() const noexcept
    {
        return std::string_view(std::bit_cast<char const*>(this->data()),
                                this->size());
    }

    [[nodiscard]] std::string_view tosv() const noexcept
    {
        return *this;
    }

    [[nodiscard]] std::string tos() const noexcept
    {
        return std::string(std::bit_cast<char const*>(this->data()),
                           this->size());
    }
};

class MemSpan final : public std::span<std::byte>
{
    using Base_ = std::span<std::byte>;

public:
    constexpr MemSpan() noexcept = default;

    template<IsMemArea T>
    requires EXPR(!IsConst<T>)
    explicit(!(IsIdentical<T, Buffer> ||
               IsIdentical<T, std::string>)) constexpr MemSpan(T& mm) noexcept
        : Base_(getMemAreaInfo(mm).first, getMemAreaInfo(mm).second)
    {}

    constexpr MemSpan(void* const p, IsNumberLike auto const size) noexcept
        : Base_(std::bit_cast<std::byte*>(p), Number(size))
    {}

    [[nodiscard]] constexpr operator MemView() const noexcept
    {
        return MemView(*this);
    }

    [[nodiscard]] constexpr operator std::string_view() const noexcept
    {
        return std::string_view(std::bit_cast<char const*>(this->data()),
                                this->size());
    }

    [[nodiscard]] std::string_view tosv() const noexcept
    {
        return *this;
    }

    [[nodiscard]] std::string tos() const noexcept
    {
        return std::string(std::bit_cast<char const*>(this->data()),
                           this->size());
    }
};

template<typename T>
concept IsMemSpanRange = IsRange<T> && requires
{
    requires IsIdentical<RangeValueType<T>, MemSpan>;
};

template<typename T>
concept IsMemViewRange = IsRange<T> && requires
{
    requires IsIdentical<RangeValueType<T>, MemView>;
};

[[nodiscard]] constexpr char
getLowHalfHexChar(IsOctet auto const octet,
                  SbLowercase const  sb_lowercase) noexcept
{
    auto const c = std::bit_cast<unsigned char>(octet);
    if (sb_lowercase)
    {
        return "0123456789abcdef"sv[c & 0x0f_u8];
    }

    return "0123456789ABCDEF"sv[c & 0x0f_u8];
}

[[nodiscard]] constexpr char
getHighHalfHexChar(IsOctet auto const octet,
                   SbLowercase const  sb_lowercase) noexcept
{
    auto const c = std::bit_cast<std::byte>(octet);
    return getLowHalfHexChar(c >> 4_u8, sb_lowercase);
}

std::size_t hex2bin(std::string_view const sv_hex,
                    void* const            obuf,
                    std::size_t const      obuf_size);

[[nodiscard]] Buffer hex2bin(std::string_view const sv_hex);

[[nodiscard]] std::string bin2hex(void const* const buf,
                                  std::size_t const size,
                                  SbLowercase const sb_lowercase);

[[nodiscard]] std::string bin2hex(void const* const buf,
                                  std::size_t const size);

[[nodiscard]] inline std::string bin2hex(IsMemArea auto const& mm,
                                         SbLowercase const     sb_lowercase)
{
    auto const [p, size] = getMemAreaInfo(mm);
    return bin2hex(p, size, sb_lowercase);
}

[[nodiscard]] inline std::string bin2hex(IsMemArea auto const& mm)
{
    auto const [p, size] = getMemAreaInfo(mm);
    return bin2hex(p, size, SbLowercase{});
}

inline void zero(IsMemArea auto& mm)
{
    auto const [data, size] = getMemAreaInfo(mm);
    std::memset(data, 0, size);
}

template<std::size_t N>
class ByteArray : public std::array<std::byte, N>
{
public:
    explicit constexpr ByteArray(bool const zeroized) noexcept
    {
        if (zeroized)
        {
            zero(*this);
        }
    }

    constexpr ByteArray() noexcept : ByteArray(false)
    {}

    explicit ByteArray(void const* const p_data)
    {
        this->load(p_data);
    }

    [[nodiscard]] std::size_t getHashCode() const noexcept
    {
        if constexpr (N >= sizeof(std::size_t))
        {
            return *std::bit_cast<std::size_t const*>(this->data());
        }
        else
        {
            auto n = std::size_t{};
            std::memcpy(&n, this->data(), N);
            return n;
        }
    }

    void load(void const* const p_data)
    {
        std::memcpy(this->data(), p_data, N);
    }
};

template<std::default_initializable Value_, IsNonVoid Mutex_ = std::mutex>
requires requires(Mutex_& mtx)
{
    mtx.lock();
    mtx.unlock();
    requires IsRaw<Value_>;
}
class Atomic final
{
    mutable Mutex_ mtx_;
    Value_         value_;

public:
    Atomic() noexcept = default;

    template<IsNonVoid... Args_>
    requires EXPR(std::constructible_from<Value_, Args_...>)
    explicit Atomic(Args_&&... args) : value_(FORWARD(args)...)
    {}

    decltype(auto) visit(auto fn)
    {
        static_assert(IsCallable<decltype(fn), Value_&>);
        LOCK(this->mtx_);
        return std::invoke(std::move(fn), this->value_);
    }

    decltype(auto) visit(auto fn) const
    {
        static_assert(IsCallable<decltype(fn), Value_ const&>);
        LOCK(this->mtx_);
        return std::invoke(std::move(fn), this->value_);
    }
};

template<IsNonVoid T>
requires IsRaw<T>
struct Hash final
{
    using is_transparent = void;

    [[nodiscard]] constexpr auto operator()(auto const& obj) const noexcept
    {
        using U_ = NoCvr<decltype(obj)>;
        if constexpr (requires { std::hash<U_>{}(obj); })
        {
            return std::hash<U_>{}(obj);
        }
        else if constexpr (requires { obj.getHashCode(); })
        {
            return obj.getHashCode();
        }
        else
        {
            return InvalidType{};
        }
    }
};

template<IsNonVoid Key_,
         IsNonVoid Compare_   = std::less<>,
         IsNonVoid Allocator_ = std::allocator<Key_>>
using Set = std::set<Key_, Compare_, Allocator_>;

template<IsNonVoid Key_,
         IsNonVoid Compare_   = std::less<>,
         IsNonVoid Allocator_ = std::allocator<Key_>>
using Multiset = std::multiset<Key_, Compare_, Allocator_>;

template<IsNonVoid Key_,
         IsNonVoid T,
         IsNonVoid Compare_   = std::less<>,
         IsNonVoid Allocator_ = std::allocator<std::pair<Key_ const, T>>>
using Map = std::map<Key_, T, Compare_, Allocator_>;

template<IsNonVoid Key_,
         IsNonVoid T,
         IsNonVoid Compare_   = std::less<>,
         IsNonVoid Allocator_ = std::allocator<std::pair<Key_ const, T>>>
using Multimap = std::multimap<Key_, T, Compare_, Allocator_>;

template<IsNonVoid Key_,
         IsNonVoid Hash_      = Hash<Key_>,
         IsNonVoid KeyEqual_  = std::equal_to<>,
         IsNonVoid Allocator_ = std::allocator<Key_>>
using UnorderedSet = std::unordered_set<Key_, Hash_, KeyEqual_, Allocator_>;

template<IsNonVoid Key_,
         IsNonVoid Hash_      = Hash<Key_>,
         IsNonVoid KeyEqual_  = std::equal_to<>,
         IsNonVoid Allocator_ = std::allocator<Key_>>
using UnorderedMultiset =
    std::unordered_multiset<Key_, Hash_, KeyEqual_, Allocator_>;

template<IsNonVoid Key_,
         IsNonVoid T,
         IsNonVoid Hash_      = Hash<Key_>,
         IsNonVoid KeyEqual_  = std::equal_to<>,
         IsNonVoid Allocator_ = std::allocator<std::pair<Key_ const, T>>>
using UnorderedMap = std::unordered_map<Key_, T, Hash_, KeyEqual_, Allocator_>;

template<IsNonVoid Key_,
         IsNonVoid T,
         IsNonVoid Hash_      = Hash<Key_>,
         IsNonVoid KeyEqual_  = std::equal_to<>,
         IsNonVoid Allocator_ = std::allocator<std::pair<Key_ const, T>>>
using UnorderedMultimap =
    std::unordered_multimap<Key_, T, Hash_, KeyEqual_, Allocator_>;
} // namespace stx